4

从for循环看let和var的区别


MDN的let:

let允许你声明一个作用域被限制在块级中的变量、语句或者表达式。与var关键字不同的是,var声明的变量只能是全局或者整个函数块的。在 ECMAScript 2015 中,let 绑定不受变量提升的约束,这意味着 let 声明不会被提升到当前执行上下文的顶部。在块中的变量初始化之前,引用它将会导致 ReferenceError(而使用 var 声明变量则恰恰相反,该变量的值是 undefined )。这个变量处于从块开始到 let 初始化处理的”暂存死区“之中。
循环定义中的let作用域:循环体中是可以引用在for声明时用let定义的变量,尽管let不是出现在大括号之间.

var list = document.getElementById("list");

for (let i = 1; i <= 5; i++) {
  var item = document.createElement("LI");
  item.appendChild(document.createTextNode("Item " + i));
  let j = i;
  item.onclick = function (ev) {
    console.log("Item " + j + " is clicked.");
  };
  list.appendChild(item);
}

在每次循环的时候用 let j 保留的 i 的值,所以在 i 变化的时候,j 并不会变化。而console.log 的是 j,即使此处i声明换成var,结果依然一样.
MDN此处的写法是多余的,去掉let j = i依然可以实现上述效果,只是方便理解let.事实上去掉let j = i,等效于:

for (let i = 1; i <= 5; i++) {
  //let i = i;即将i的作用域放在了改块级作用域中
  var item = document.createElement("LI");
  item.appendChild(document.createTextNode("Item " + i));
  item.onclick = function (ev) {
    console.log("Item " + i + " is clicked.");
  };
  list.appendChild(item);
}
console.log(i)// Uncaught ReferenceError: i is not defined

上边代码中最后的ReferenceError表明,i虽然在for循环{}的外层,但是实际上是被绑定在该块级作用域内的.这里和var是不一样的,var声明的i在外层,因此成为全局变量.

变量提升(hoist)

let x = "global";
(function() {
    console.log(x); // Uncaught ReferenceError: x is not defined
    let x = 'part';
    console.log(x)
}());

众所周知,let变量没有hoist,按这个理解,函数内第一行log(x)应当是global才对,但是却报错了.去掉let x = 'part'就没有问题,这说明后边的声明影响到了第一行的log(x).

fn();
function fn(){
  var x = 1;
  console.log(x,y)
  var y = 2
}

执行过程:

  1. 找到所有用 function 声明的变量,在环境中「创建」这些变量。

  2. 将这些变量「初始化」并「赋值」为 function(){//具体内容}

  3. 执行代码,即fn()

  4. 函数体内找到所有用var声明的变量,在环境中「创建」这些变量

  5. 将这些变量「初始化」为 undefined。

  6. 开始执行代码,x = 1 将 x 变量「赋值」为 1

  7. 打印x = 1 ,而y还未赋值,因此为undefined
    如果var改为let:

  8. 找到所有用 function 声明的变量,在环境中「创建」这些变量。

  9. 将这些变量「初始化」并「赋值」为 function(){//具体内容}

  10. 执行代码,即fn()

  11. 函数体内找到所有用let声明的变量,在环境中「创建」这些变量

  12. 开始执行代码,x = 1 将 x 变量「赋值」为 1

  13. 打印 x = 1 ,而y还未「初始化」,因此Uncaught ReferenceError.这就是所谓暂时性死区.


逺方小鎭
1.3k 声望165 粉丝